import socket
import struct
import random
import sys
import os
from collections import OrderedDict

class ProtocolError(Exception):
    pass

def process_file(file_path, Lmin, Lmax):
    """分块处理文件"""
    with open(file_path, 'r') as f:
        content = f.read()
    # 文件内容验证
    if not content.isascii() or any(not c.isprintable() for c in content):
        raise ProtocolError("文件包含非ASCII可打印字符")

    # 文件内容分块
    blocks = []
    pos = 0
    content_bytes = content.encode('utf-8')
    total = len(content_bytes)

    while pos < total:
        # 分成随机大小的数据块
        size = random.randint(Lmin, Lmax) if (total - pos) > Lmax else (total - pos)
        blocks.append(content_bytes[pos:pos + size])
        pos += size

    return blocks


def recv_exact(sock, length):
    """可靠接收指定长度数据"""
    data = b''
    while len(data) < length:
        packet = sock.recv(length - len(data))
        if not packet:
            raise ConnectionError("服务器断开")
        data += packet
    return data


def run_client(file_path, server_ip, server_port, Lmin, Lmax):
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.connect((server_ip, server_port))
            print(f"[CLIENT] 连接到 {server_ip}:{server_port}")

            # 1. 分块处理文件
            blocks = process_file(file_path, Lmin, Lmax)
            N = len(blocks)
            print(f"[CLIENT] 分块完成，共 {N} 块")

            # 2. 发送初始化报文 (Type=1, N)
            s.sendall(struct.pack('!HI', 1, N))

            # 3. 接收同意报文 (Type=2, 1)
            agree_data = recv_exact(s, 6)
            agree_type, _ = struct.unpack('!HI', agree_data)
            if agree_type != 2:
                raise ProtocolError("服务器拒绝请求")

            # 4. 发送数据块并接收反转结果
            received = OrderedDict()
            for bkey, block in enumerate(blocks, 1):
                # 构造请求报文 (Type=3, bkey, length, data)
                req_header = struct.pack('!HII', 3, bkey, len(block))
                s.sendall(req_header + block)

                # 接收响应报文 (Type=4, bkey, length, reversed_data)
                resp_header = recv_exact(s, 10)
                resp_type, resp_bkey, length = struct.unpack('!HII', resp_header)
                reversed_data = recv_exact(s, length).decode('utf-8')

                if resp_type != 4 or resp_bkey != bkey:
                    raise ProtocolError(f"块 {bkey} 响应错误")

                received[bkey] = reversed_data
                print(f"第 {bkey} 块：{reversed_data}")

            # 5. 保存完整反转结果
            output_path = f"{os.path.splitext(file_path)[0]}_reversed.txt"
            with open(output_path, 'w') as f:
                f.write(''.join(reversed(received.values())))
            print(f"[CLIENT] 结果已保存至 {output_path}")

            # 6. 客户端主动关闭连接（发起 FIN）
            s.close()  # 客户端主动关闭，触发四次挥手
            print("[CLIENT] 主动关闭连接")

    except ProtocolError as e:
        print(f"[协议错误] {str(e)}")
    except ConnectionError as e:
        print(f"[连接错误] {str(e)}")
    except Exception as e:
        print(f"[错误] {repr(e)}")
    finally:
        s.close()


if __name__ == "__main__":
    if len(sys.argv) != 6:
        print("用法: python client.py <文件路径> <服务器IP> <端口> <Lmin> <Lmax>")
        sys.exit(1)

    # 获取命令行的参数
    file_path, server_ip, port, lmin, lmax = sys.argv[1:]
    try:
        run_client(file_path, server_ip, int(port), int(lmin), int(lmax))
    except ValueError:
        print("错误: 端口和块大小必须为整数")
        sys.exit(1)